module apb_delayer(
  input         clock,
  input         reset,
  input  [31:0] in_paddr,
  input         in_psel,
  input         in_penable,
  input  [2:0]  in_pprot,
  input         in_pwrite,
  input  [31:0] in_pwdata,
  input  [3:0]  in_pstrb,
  output        in_pready,
  output [31:0] in_prdata,
  output        in_pslverr,

  output [31:0] out_paddr,
  output        out_psel,
  output        out_penable,
  output [2:0]  out_pprot,
  output        out_pwrite,
  output [31:0] out_pwdata,
  output [3:0]  out_pstrb,
  input         out_pready,
  input  [31:0] out_prdata,
  input         out_pslverr
);
  localparam INCREMENT = 5402; // r * s = 5.2758 * 1024

  wire in_valid;
  wire pos_in_valid;
  wire access_sdram;

  reg        pready;
  reg [31:0] prdata;
  reg        pslverr;

  reg pre_in_valid;
  reg pre_in_penable;

  reg enable; // enable to count
  reg [63:0] counter; // record total delay
  reg [63:0] pre_counter; // record the number of cycles from receiving a request to receiving an SDRAM replay

  assign in_valid = in_psel;
  assign pos_in_valid = ~pre_in_valid & in_valid;
  assign access_sdram = in_paddr >= 32'ha000_0000 && in_paddr < 32'hc000_0000;

  always @(posedge clock or posedge reset) begin
    if (reset) pre_in_valid <= 1'b0;
    else pre_in_valid <= in_valid;
  end

  // count the number of delayed cycles
  always @(posedge clock or posedge reset) begin
    if (reset) enable <= 1'b0;
    else if (out_pready) enable <= 1'b0;    // out_pready lasts for one cycle
    else if (pos_in_valid) enable <= 1'b1;
  end
  always @(posedge clock or posedge reset) begin
    if (reset) begin
      counter <= 0;
      pre_counter <= 0;
    end
    else if (pos_in_valid) begin
      counter <= 2 * INCREMENT;     // before enable asserts, in_valid has lasted for two cycles
      pre_counter <= 2;
    end
    else if (out_pready) begin
      counter <= (counter >> 10) - pre_counter;
      pre_counter <= pre_counter;
    end
    else if (enable & ~out_pready) begin
      counter <= counter + INCREMENT;
      pre_counter <= pre_counter + 1;
    end
    else if (pready) begin
      counter <= counter - 1;
      pre_counter <= pre_counter;
    end
  end

  // set pready
  always @(posedge clock or posedge reset) begin
    if (reset) pready <= 1'b0;
    else if (pready && counter == 1) pready <= 1'b0;
    else if (~pready & out_pready & access_sdram) pready <= 1'b1;
  end

  // get prdata and pslverr
  always @(posedge clock or posedge reset) begin
    if (reset) begin
      prdata  <= 32'd0;
      pslverr <= 1'b0;
    end
    else if (out_pready) begin
      prdata  <= out_prdata;
      pslverr <= out_pslverr;
    end
  end

  assign out_paddr   = in_paddr;
  assign out_psel    = in_psel;
  assign out_penable = in_penable;
  assign out_pprot   = in_pprot;
  assign out_pwrite  = in_pwrite;
  assign out_pwdata  = in_pwdata;
  assign out_pstrb   = in_pstrb;
  assign in_pready   = ~access_sdram ? out_pready  : counter == 1 ? pready : 0;
  assign in_prdata   = ~access_sdram ? out_prdata  : prdata;
  assign in_pslverr  = ~access_sdram ? out_pslverr : pslverr;

endmodule
